use std::{collections::HashMap, sync::Arc, io::{Seek, Read, Write}};

use crate::{chunk::Chunk, constants::{XCONTAINER_LEN, DefaultFingerprintType}, recipe::ChunkPointer, xrechunk::XreChunk};

use super::{XcontainerId, DynContainer, DynContainerWriteItem, XcontainerResult, FixedContainer, OffsetMeta};

pub type XChunkMeta = <Chunk as DynContainerWriteItem>::Meta;

pub struct XcontainerWriter {
    id:XcontainerId,
    fp_table:HashMap<DefaultFingerprintType,usize>,
    inner:DynContainer<XChunkMeta,Chunk,XCONTAINER_LEN>,
}

impl XcontainerWriter {
    pub fn new(id:XcontainerId) -> Self {
        Self {
            id:id,
            fp_table:HashMap::new(),
            inner:DynContainer::new(),
        }
    }

    pub fn half(id:XcontainerId,meta:Arc<FixedContainer<(OffsetMeta,XChunkMeta),XCONTAINER_LEN>>) -> Self {
        let fp_table:HashMap<_,_> = meta
            .iter()
            .map(|(no,(_kind,fp))|(*fp,*no as usize))
            .collect();
        Self {
            id:id,
            fp_table:fp_table,
            inner:DynContainer::half(meta),
        }
    }

    pub fn id(&self) -> XcontainerId {
        self.id
    }

    pub fn inner(&self) -> &DynContainer<XChunkMeta,Chunk,XCONTAINER_LEN> {
        &self.inner
    }

    pub fn append_chunk<P:ChunkPointer>(&mut self,chunk:Arc<Chunk>) -> XcontainerResult<P> {
        let new_chunk:Arc<Chunk> = chunk.clone();
        let no = self.fp_table.get(new_chunk.fp());
        match no {
            Some(no) => {
                new_chunk.set_id_no(self.id(),(*no).try_into().unwrap());
                Ok(P::new(self.id(), (*no).try_into().unwrap(), new_chunk.fp()))
            }
            None => {
                let no = self.inner.append_item(chunk.clone())?;
                self.fp_table.insert(*chunk.fp(), no);
                new_chunk.set_id_no(self.id(),no.try_into().unwrap());
                Ok(P::new(self.id(), no.try_into().unwrap(), new_chunk.fp()))
            }
        }
    }

    pub fn find(&self,fp:&DefaultFingerprintType) -> Option<Arc<Chunk>> {
        if let Some(&no) = self.fp_table.get(fp) {
            self.inner.get(no).map(|c|c.clone())
        } else {
            None
        }
    }

    pub fn find_chunk_pointer<P:ChunkPointer>(&self,fp:&DefaultFingerprintType) -> Option<Box<P>> {
        if let Some(&no) = self.fp_table.get(fp) {
            Some(Box::new(P::new(self.id(), no.try_into().unwrap(), fp)))
        } else {
            None
        }
    }

    pub fn on_write<W:Write+Seek>(&self,writer:&mut W) -> XcontainerResult<(u64,Arc<FixedContainer<(OffsetMeta,XChunkMeta),XCONTAINER_LEN>>)> {
        self.inner.on_vec_write(writer)
    }
}

pub struct XcontainerReader {
    id:XcontainerId,
    fp_table:HashMap<DefaultFingerprintType,usize>,
    inner:DynContainer<XChunkMeta,XreChunk,XCONTAINER_LEN>,
}

impl XcontainerReader {
    pub fn on_read<F:Seek+Read>(id:XcontainerId,file:&mut F,meta:Arc<FixedContainer<(OffsetMeta,XChunkMeta),XCONTAINER_LEN>>) -> XcontainerResult<Self> {
        let inner = DynContainer::<XChunkMeta,XreChunk,XCONTAINER_LEN>::on_vec_read(file,meta)?;
        let mut fp_table = HashMap::new();
        for (i,meta) in inner.meta().iter().enumerate() {
            fp_table.insert(meta.1.1, i as usize);
        }
        Ok(Self {
            id:id,
            fp_table:fp_table,
            inner:inner,
        })
    }

    pub fn id(&self) -> XcontainerId {
        self.id
    }

    pub fn find(&self,fp:&DefaultFingerprintType) -> Option<Arc<XreChunk>> {
        if let Some(&no) = self.fp_table.get(fp) {
            self.inner.get(no).map(|c|c.clone())
        } else {
            None
        }
    }

    pub fn find_chunk_pointer<P:ChunkPointer>(&self,fp:&DefaultFingerprintType) -> Option<Box<P>> {
        if let Some(&no) = self.fp_table.get(fp) {
            Some(Box::new(P::new(self.id(), no.try_into().unwrap(), fp)))
        } else {
            None
        }
    }
}